﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Xceed.Silverlight.DataGrid
{
  internal class LinearLayout : ILayout
  {
    public LinearLayout( ILayoutContainer panel, IEnumerable<ILayoutElement> elements, Orientation orientation )
    {
      this.Owner = panel;
      m_orientation = orientation;
      m_elements = elements;
    }

    protected ILayoutContainer Owner
    {
      get;
      set;
    }

    public Orientation Orientation
    {
      get
      {
        return m_orientation;
      }
    }

    public IEnumerable<ILayoutElement> Elements
    {
      get
      {
        return m_elements;
      }
    }

    public Size DesiredSize
    {
      get
      {
        Size size = new Size( 0, 0 );
        foreach( ILayoutElement element in this.Elements )
        {
          Size desiredSize = element.DesiredSize.Convert( this.Orientation );
          size.Height += desiredSize.Height;
          size.Width = Math.Max( size.Width, desiredSize.Width );
        }

        return size.ConvertBack( this.Orientation );
      }

    }

    public virtual void SetViewportPosition( ViewportPosition viewportPosition )
    {
      m_viewportPosition = viewportPosition;
    }

    public Size MeasurePanel( Size availableSize )
    {
      Size desiredSize = this.MeasurePanelCore( this.FlipSizeToOrientation( availableSize ) );
      return this.FlipSizeToOrientation( desiredSize );
    }

    protected virtual Size MeasurePanelCore( Size availableSize )
    {
      Size desiredSize = new Size( 0, 0 );
      Size childrenAvailableSize = availableSize;
      childrenAvailableSize.Height = double.PositiveInfinity;

      foreach( ILayoutElement child in this.Elements )
      {
        Size childDesiredSize = this.MeasureChildCore( child, childrenAvailableSize );
        desiredSize.Height += childDesiredSize.Height;
      }

      if( m_viewportPosition != null )
      {
        double measuredLength = desiredSize.Height;
        double availableLength = availableSize.Height;
        double arrangeOffset = this.ResolveArrangeOffsetCore( availableLength, m_viewportPosition );

        if( arrangeOffset >= 0 )
        {
          desiredSize.Height = measuredLength - Math.Min( measuredLength, arrangeOffset );
        }
        else
        {
          desiredSize.Height = Math.Max( 0d, availableLength + arrangeOffset );
        }
      }

      return desiredSize;
    }

    private double ResolveArrangeOffsetCore( double viewHeight, ViewportPosition viewPosition )
    {
      double positionOffset = 0d;

      object firstItem = viewPosition.Item;
      if( firstItem == null )
      {
        if( !viewPosition.AlignToFirstEdge )
        {
          double desiredLength = this.Elements.Sum( ( e ) => this.GetChildDesiredSizeCore( e ).Height );
            positionOffset = desiredLength - viewHeight;
          }
        }
      else
      {
        ItemOffsetInfo offsetInfo = this.GetItemOffsetInfo( firstItem );
        double offsetLength = viewPosition.OffscreenPositionLength.ConvertToPixels( offsetInfo.Length );

        if( viewPosition.AlignToFirstEdge )
        {
          positionOffset = offsetInfo.Offset - offsetInfo.ParentsHeadersLength;
          positionOffset += offsetLength;
        }
        else
        {
          positionOffset = ( offsetInfo.Offset + offsetInfo.Length );
          double alignEdge = viewHeight - offsetInfo.ParentsFootersLength;
          positionOffset -= offsetLength;
          positionOffset -= alignEdge;

          Debug.Assert( offsetLength == 0 );
        }
      }

      return positionOffset;
    }

    private ItemOffsetInfo GetItemOffsetInfo( object item )
    {
      ItemOffsetInfo offsetInfo = new ItemOffsetInfo( item );

      if( !this.GetItemOffsetInfo( this.Owner, offsetInfo ) )
        throw new DataGridInternalException();

      return offsetInfo;
    }

    private bool GetItemOffsetInfo( ILayoutContainer container, ItemOffsetInfo info )
    {
      LinearLayout layout = this.GetLinearLayout( container.Layout );
      double headersLength = this.GetHeaderDesiredSizeCore( container ).Height;
      double footersLength = this.GetFooterDesiredSizeCore( container ).Height;

      info.Offset += headersLength;

      info.ParentsHeadersLength += headersLength;
      info.ParentsFootersLength += footersLength;

      foreach( ILayoutElement element in layout.Elements )
      {
        if( object.Equals( element.ReferenceItem, info.Item ) )
        {
          info.Length = this.GetChildDesiredSizeCore( element ).Height;
          return true;
        }

        if( this.IsLeaf( element ) )
        {
          info.Offset += this.GetChildDesiredSizeCore( element ).Height;
        }
        else
        {
          if( this.GetItemOffsetInfo( ( VirtualContainerBase )element, info ) )
            return true;
        }
      }

      info.ParentsHeadersLength -= headersLength;
      info.ParentsFootersLength -= footersLength;

      info.Offset += footersLength;
      return false;
    }

    private bool IsLeaf( ILayoutElement element )
    {
      if( !( element is VirtualContainerBase ) )
        return true;

      LinearLayout linearLayout = this.GetLinearLayout( ( ( VirtualContainerBase )element ).Layout );
      return !linearLayout.Elements.Any();
    }

    private Size GetHeaderDesiredSizeCore( ILayoutContainer container )
    {
      return ( container is VirtualContainerBase )
        ? ( ( VirtualContainerBase )container ).HeadersDesiredSize.Convert( this.Orientation )
        : new Size( 0, 0 );
    }

    private Size GetFooterDesiredSizeCore( ILayoutContainer container )
    {
      return ( container is VirtualContainerBase )
        ? ( ( VirtualContainerBase )container ).FootersDesiredSize.Convert( this.Orientation )
        : new Size( 0, 0 );
    }

    private LinearLayout GetLinearLayout( ILayout layout )
    {
      if( layout is LinearLayout )
      {
        return ( LinearLayout )layout;
      }
      else if( layout is UpdateTransitionLayout )
      {
        return this.GetLinearLayout( ( ( UpdateTransitionLayout )layout ).NewLayout );
      }

      throw new DataGridInternalException();
    }

    private Size MeasureChildCore( ILayoutElement child, Size size )
    {
      this.Owner.MeasureElement( child, this.FlipSizeToOrientation( size ) );
      return this.FlipSizeToOrientation( child.DesiredSize );
    }

    public Size ArrangePanel( Size finalSize, LayoutParameters parameters )
    {
      Size arrangeSize = this.ArrangePanelCore( this.FlipSizeToOrientation( finalSize ), parameters );
      return finalSize;
    }


    protected virtual Size ArrangePanelCore( Size finalSize, LayoutParameters parameters )
    {
      Rect currentPosition = new Rect( 0, 0d, finalSize.Width, 0 );
      Size arrangedSize = new Size( 0, 0 );

      foreach( ILayoutElement layoutChild in m_elements )
      {
        Size childDesiredSize = this.GetChildDesiredSizeCore( layoutChild );
        currentPosition.Height = childDesiredSize.Height;
        this.ArrangeChildCore( layoutChild, currentPosition, finalSize.Height, parameters );
        currentPosition.Y += childDesiredSize.Height;
        arrangedSize.Height += childDesiredSize.Height;
        arrangedSize.Width = Math.Max( arrangedSize.Width, currentPosition.Width );
      }

      return arrangedSize;
    }

    protected void ArrangeChildCore( ILayoutElement layoutChild, Rect rect, double finalHeight, LayoutParameters parameters )
    {
      rect = this.ConvertBackToAbsoluteLayout( rect );

      if( m_viewportPosition != null )
      {
        double arrangeOffset = this.ResolveArrangeOffsetCore( finalHeight, m_viewportPosition );
        arrangeOffset = Math.Max( 0d, arrangeOffset );
        rect = rect.SetOrientedPosition( this.Orientation, rect.GetOrientedPosition( this.Orientation ) - arrangeOffset );
      }

      this.Owner.ArrangeElement( layoutChild, rect, parameters );
    }

    public ViewportInfo GetViewportInfo( Rect viewportRect )
    {
      return this.GetViewportInfoCore( viewportRect.Convert( this.Orientation ) );
    }

    private ViewportInfo GetViewportInfoCore( Rect viewportRect )
    {
      ViewportInfo viewInfo = new ViewportInfo();

      if( m_viewportPosition != null )
      {
        viewportRect.Y += Math.Max( 0d, this.ResolveArrangeOffsetCore( viewportRect.Height, m_viewportPosition ) );
      }

      double currentOffset = 0d;
      List<ILayoutElement> elements = this.Elements.ToList();
      for( int i = 0; i < elements.Count; i++ )
      {
        ILayoutElement element = elements[ i ];
        bool isLast = ( i == elements.Count - 1 );
        Size desiredSize = this.GetChildDesiredSizeCore( element );

        if( ( currentOffset + desiredSize.Height > viewportRect.Y ) || isLast )
        {
          if( ( element is VirtualContainerBase )
            && this.GetHasElements( ( VirtualContainerBase )element ) )
          {
            Rect childViewRect = viewportRect;
            childViewRect.Y = Math.Max( 0d, viewportRect.Y - currentOffset );
            childViewRect.Height -= Math.Max( 0d, currentOffset - viewportRect.Y );
            ViewportInfo childInfo = ( ( VirtualContainerBase )element ).GetViewportInfo( childViewRect.FlipRectOrientation( this.Orientation ) );

            Debug.Assert( viewInfo.Elements.Count == 0 || childInfo.FirstElementOffscreenLength == 0d );

            if( viewInfo.Elements.Count == 0 )
            {
              viewInfo.FirstElementLength = childInfo.FirstElementLength;
              viewInfo.FirstElementOffscreenLength = childInfo.FirstElementOffscreenLength;
            }

            foreach( ILayoutElement subElement in childInfo.Elements )
            {
              viewInfo.Elements.Add( subElement );
            }

            viewInfo.LastElementLength = childInfo.LastElementLength;
            viewInfo.LastElementOffscreenLength = childInfo.LastElementOffscreenLength;
          }
          else
          {
            viewInfo.Elements.Add( element );
            if( viewInfo.Elements.Count == 1 )
            {
              viewInfo.FirstElementLength = desiredSize.Height;
              viewInfo.FirstElementOffscreenLength = Math.Max( 0d, ( viewportRect.Y - currentOffset ) );
            }

            viewInfo.LastElementLength = desiredSize.Height;
            viewInfo.LastElementOffscreenLength = Math.Max( 0d, ( currentOffset + desiredSize.Height ) - ( viewportRect.Y + viewportRect.Height ) );
          }
        }

        currentOffset += desiredSize.Height;

        if( currentOffset >= viewportRect.Y + viewportRect.Height )
          break;
      }

      return viewInfo;
    }

    private bool GetHasElements( VirtualContainerBase container )
    {
      ITransitionLayout transitionLayout = container.Layout as ITransitionLayout;
      IEnumerable<ILayoutElement> viewChildren = ( transitionLayout != null )
        ? transitionLayout.NewLayout.Elements
        : container.Elements;

      return viewChildren.Any();
    }

    private Rect ConvertBackToAbsoluteLayout( Rect rect )
    {
      return rect.ConvertBack( this.Orientation );
    }

    protected Size GetChildDesiredSizeCore( ILayoutElement child )
    {
      return this.FlipSizeToOrientation( child.DesiredSize );
    }

    private Size FlipSizeToOrientation( Size size )
    {
      return size.Convert( this.Orientation );
    }


    private Orientation m_orientation;
    private IEnumerable<ILayoutElement> m_elements;
    private ViewportPosition m_viewportPosition;
  }
}
